/* Copyright © 2023 - 2024 Coremail论客
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { ContentType,
  EmailAddress,
  MimeDisposition,
  MimeDispositionType,
  MimeHeader,
  MimeParams } from '../api';
import { base64Encode, decodeMimeString,
  decodeParameterString,
  encodeMimeString } from './encodings';
import { getLogger } from './log';

const logger = getLogger('mime-helper')

export enum MimeHeaderName {
  ContentType = 'Content-Type',
  ContentDisposition = 'Content-Disposition',
  ContentTransferEncoding = 'Content-Transfer-Encoding',
  From = 'From',
  To = 'To',
  CC = 'Cc',
  BCC = 'Bcc',
  Subject = 'Subject',
  Date = 'Date',
  MessageID = 'Message-ID',
  ContentID = 'Content-ID',
  Received = 'Received',
  XPriority = 'X-Priority',
}

let _boundaryPrefix = "----=_Part_";
let _boundarySuffix: string | undefined;
let _suffixIndex = 1;

export function setBoundaryPattern(suffix: string, prefix = '----=_Part_'): void {
  _boundarySuffix = suffix;
  _boundaryPrefix = prefix;
}

export function generateBoundary(index: number): string {
  let suffix = _boundarySuffix;
  if (!suffix) {
    suffix = `${_suffixIndex}.${Math.random().toString().substring(2)}`;
    _suffixIndex += 1;
  }
  return `${_boundaryPrefix}${index}.${suffix}`;
}

export function generateMessageId(): string {
  return `<${base64Encode(`core${(new Date).toISOString()}${Math.random().toString().substring(2)}mail`)}>`
}

export function parseMimeParams(s: string): MimeParams {
  const params = s.split(';')
  const map: MimeParams = {};
  const rfc2231Map: Map<string, [[string, number][], boolean]> = new Map;
  for (const p of params) {
    const i = p.indexOf("=");
    if (i < 0) {
      map[p] = p;
      continue;
    }
    let name = p.substring(0, i).trim();
    if (['name', 'filename', 'charset'].includes(name.toLowerCase())) {
      name = name.toLowerCase();
    }
    let value = p.substring(i + 1).trim();
    if (value.startsWith('"') && value.endsWith('"')) {
      value = value.substring(1, value.length - 1);
    }
    if (name.indexOf('*') > 0) {
      let [trueName, index, needDecode] = name.split('*');
      let i = parseInt(index);
      const v = rfc2231Map.get(trueName) || [[], true];
      v[0].push([value, i]);
      if (i == 0) {
        v[1] = needDecode !== undefined
      }
      rfc2231Map.set(trueName, v);
    } else {
      value = decodeMimeString(value);
      if (name == 'charset') {
        value = value.toLowerCase();
      }
      map[name] = value;
    }
  }
  for (const [name, value] of rfc2231Map.entries()) {
    const [values, needDecode] = value;
    values.sort((v1, v2) => v1[1] < v2[1] ? -1 : 1);
    let s = values.map(v => v[0]).join('');
    if (needDecode) {
      s = decodeParameterString(s);
    }
    map[name] = s;
  }
  return map;
}

function checkAndEncodeParamString(k: string, v: string) {
  return ['charset', 'boundary'].includes(k) ? v : encodeMimeString(v);
}

export function stringifyMimeParams(params: MimeParams): string {
  const p = Object.entries(params)
    .map(([k, v]) => `${k}="${checkAndEncodeParamString(k, v)}"`)
    .join(";\r\n\t");
  return p;
}

export function parseContentType(s: string): ContentType {
  const idx = s.indexOf(';');
  let params = '';
  if (idx > 0) {
    params = s.substring(idx + 1);
    s = s.substring(0, idx);
  }
  const [type, subType] = s.split("/");
  return {
    type: type.toLowerCase(),
    subType: subType.toLowerCase(),
    params: parseMimeParams(params),
  };
}

export function stringifyContentType(contentType: ContentType): string {
  const s = `${contentType.type}/${contentType.subType}`
  const p = stringifyMimeParams(contentType.params);
  if (p) {
    return `${s}; ${p}`;
  } else {
    return s;
  }
}

export function parseDisposition(s: string): MimeDisposition {
  const idx = s.indexOf(';');
  let params = '';
  if (idx > 0) {
    params = s.substring(idx + 1);
    s = s.substring(0, idx);
  }
  return { type: s.toLowerCase() as MimeDispositionType, params: parseMimeParams(params) };
}

export function stringifyDisposition(disposition: MimeDisposition): string {
  const p = stringifyMimeParams(disposition.params);
  if (p) {
    return `${disposition.type}; ${p}`;
  } else {
    return disposition.type;
  }
}

/*
简单解析邮件地址
CAVEAT: 当名字里面有特殊字符<,;时会出错
*/
function skipSpace(s: string, loc: number): number {
  while (loc < s.length && (s[loc] == ' ' || s[loc] == '\t')) {
    loc += 1;
  }
  return loc;
}

function findFirst(s: string, chars: string[], start: number = 0): number {
  const len = s.length;
  const index = Math.min(...chars.map(ch => {
    const i = s.indexOf(ch, start);
    return i < 0 ? len : i;
  }))
  return index == len ? -1 : index;
}

export function parseAddress(s: string): EmailAddress[] {
  if (!s || !s.trim()) {
    return [];
  }
  const result: EmailAddress[] = []
  let i = 0;
  try {
    while (i < s.length) {
      i = skipSpace(s, i);
      const a: EmailAddress = {
        name: '',
        email: ''
      }
      if (s[i] == '"') { // 名字部分开始
        i += 1;
        let j = i;
        while (j < s.length) {
          let k = s.indexOf('"', j);
          if (k < 0) {
            j = s.length;
            break;
          }
          j = k;
          if (s[k - 1] != '\\') {
            break;
          }
        }
        a.name = s.substring(i, j);
        i = j + 1;
        i = skipSpace(s, i);
      } else if (s[i] != '<') { // 没有引号的情况，也不是用尖括号围起来的邮箱地址
        let j = findFirst(s, ['<', ';'], i + 1);
        if (j < 0 || s[j] == ';') {
          const e = j < 0 ? s.length : j;
          const ss = s.substring(i, e);
          if (ss.includes('@')) {
            a.email = ss;
          } else {
            a.name = ss;
          }
          result.push(a);
          i = e + 1;
          continue;
        } else { // s[j] == '<'
          const ss = s.substring(i, j).trim();
          a.name = ss;
          i = j;
        }
      }
      // assert s[i] == '<'
      i = i + 1;
      let j = s.indexOf('>', i);
      if (j < 0) {
        j = s.length;
      }
      a.email = s.substring(i, j).trim()
      result.push(a);
      i = j + 1;
      while (i < s.length && (s[i] == ';' || s[i] == ',' || s[i] == ' ' || s[i] == '\t')) {
        i += 1;
      }
    }
    return result;
  } catch (e) {
    logger.error("parseAddress error", s, e);
    return [];
  }
}

export function encodeAddress(address: EmailAddress): string {
  if (!address.name) {
    return address.email;
  }
  return `"${encodeMimeString(address.name, 0)}" <${address.email}>`;
}

export function encodeAddressList(addressList: EmailAddress[]): string {
  return addressList.map(addr => encodeAddress(addr)).join(';\r\n\t');
}

export function capitalize(s: string): string {
  const capWords = ['id'];
  return s.split('-')
    .map(s => {
      if (capWords.includes(s)) {
        return s.toUpperCase();
      }
      return s[0].toUpperCase() + s.substring(1)
    })
    .join('-');
}

export class MimeHeaderImpl implements MimeHeader {
  _headers: Map<string, string[]> = new Map;
  constructor(initHeaders?: [string, string][]) {
    if (initHeaders) {
      for (const [name, value] of initHeaders) {
        this._headers.set(name.toLowerCase(), [name, value]);
      }
    }
  }

  get size(): number {
    return this._headers.size;
  }

  get(name: string): string | undefined {
    name = name.toLowerCase();
    const values = this._headers.get(name);
    return values && values[1];
  }

  getList(name: string): string[] | undefined {
    name = name.toLowerCase();
    const values = this._headers.get(name);
    return values && values.slice(1);
  }

  replace(name: string, value: string): MimeHeader {
    name = name.toLowerCase();
    this._headers.set(name.toLowerCase(), [name, value]);
    return this;
  }

  set(name: string, value: string): MimeHeader {
    name = name.toLowerCase();
    const values = this._headers.get(name);
    if (values) {
      values.push(value);
    } else {
      this._headers.set(name.toLowerCase(), [name, value]);
    }
    return this;
  }

  has(name: string): boolean {
    name = name.toLowerCase();
    return this._headers.has(name);
  }

  delete(name: string): boolean {
    name = name.toLowerCase();
    return this._headers.delete(name);
  }

  entries(useRawName?: boolean): IterableIterator<[string, string]> {
    const e = this._headers.entries();
    let result;
    let value: string[] = [];
    let name = '';
    const getNext = () => {
      result = e.next();
      if (!result.done) {
        if (useRawName) {
          name = result.value[1][0];
        } else {
          name = result.value[0];
        }
        value = result.value[1].slice(1)
      }
    }
    const getValue = (): [string, string] | undefined => {
      if (!result.done && value.length) {
        const v = value.shift()!;
        return [name, v]
      }
      return undefined;
    }
    getNext();
    const o = {
      next(): IteratorResult<[string, string]> {
        let value = getValue();
        if (!value) {
          getNext();
          value = getValue();
        }
        if (!value) {
          return { done: true, value: undefined };
        } else {
          return { done: false, value };
        }
      },
      [Symbol.iterator]: () => o
    }
    return o;
  }
}
